extends Node

signal request_node_released

# Needed because of https://github.com/godotengine/godot/issues/25052
const MAX_CONCURRENT_REQUESTS: int = 20
const INT_MAX: int = 9223372036854775807

# Pool of HTTPRequest nodes
var _request_nodes_pool: Array = []
# Boolean array of which HTTPRequest nodes are in use
var _request_nodes_claimed: Array = []


func _ready() -> void:
	for _count in range(MAX_CONCURRENT_REQUESTS):
		var request := HTTPRequest.new()
		_request_nodes_pool.append(request)
		_request_nodes_claimed.append(false)
		add_child(request)


func get_image(url: String) -> Image:
	"""
	Args:
		url: A web URL to an image file
	
	Returns:
		An Image object of the image at `url`. If no image is found then an
		empty Image object
	"""
	yield(get_tree(), "idle_frame")
	var image: Image = Cache.get_image(url)
	if image != null:
		return image
	image = Image.new()
	if url == "":
		return image
	
	var request_response: Dictionary = yield(_make_http_request(url),
			"completed")
	if request_response.get("result") == HTTPRequest.RESULT_SUCCESS:
		var file_type: String = _get_file_type(url, request_response)
		var task_id: int = ThreadPool.queue_task(
			self,
			"_create_image_from_data",
			[file_type, request_response["body"]]
		)
		var result: GDScriptFunctionState = \
				ThreadPool.wait_for_task_completion(task_id)
		if result != null:
			image = yield(result, "completed")
		
		if not image.is_empty():
			Cache.add_image(url, image)
	return image


func get_comments(args: Dictionary) -> Dictionary:
	var comments: Dictionary = {}
	var comment_server: String = Data.get_comment_server()
	var headers := PoolStringArray(["Content-Type: application/json"])
	var data: Dictionary = {
		"jsonrpc": "2.0",
		"id": 1,
		"method": "comment.List",
		"params": args,
	}
	var data_json: String = JSON.print(data)
	
	var request_response: Dictionary = yield(
			_make_http_request(comment_server, headers, true,
					HTTPClient.METHOD_POST, data_json)
			, "completed")
	if request_response.get("result") == HTTPRequest.RESULT_SUCCESS:
		var json_result: JSONParseResult = \
				JSON.parse(request_response["body"].get_string_from_utf8())
		if json_result.get_error() == OK:
			comments = json_result.get_result().get("result", {})
	return comments


func get_comments_reactions(args: Dictionary) -> Dictionary:
	var reactions: Dictionary = {}
	var comment_server: String = Data.get_comment_server()
	var headers := PoolStringArray(["Content-Type: application/json"])
	var data: Dictionary = {
		"jsonrpc": "2.0",
		"id": 1,
		"method": "reaction.List",
		"params": args,
	}
	var data_json: String = JSON.print(data)
	
	var request_response: Dictionary = yield(
			_make_http_request(comment_server, headers, true,
					HTTPClient.METHOD_POST, data_json)
			, "completed")
	if request_response.get("result") == HTTPRequest.RESULT_SUCCESS:
		var json_result: JSONParseResult = \
				JSON.parse(request_response["body"].get_string_from_utf8())
		if json_result.get_error() == OK:
			reactions = json_result.get_result().get("result", {})
	return reactions


func _make_http_request(url: String, custom_headers:=PoolStringArray(),
		ssl_validate_domain: bool=true, method: int=0, request_data: String=""
		) -> Dictionary:
	var result: Dictionary = {}
	var response: Array = []
	var request_node_idx: int = yield(_claim_request_node(), "completed")
	var request: HTTPRequest = _request_nodes_pool[request_node_idx]
	var err = request.request(url, custom_headers, ssl_validate_domain, method,
			request_data)
	if err == OK:
		response = yield(request, "request_completed")
		result["result"] = response[0]
		result["response_code"] = response[1]
		result["headers"] = response[2]
		result["body"] = response[3]
	else:
		push_error("HTTPClient request error: %d" % err)
	_release_request_node(request_node_idx)
	return result


func _get_file_type(url: String, response: Dictionary) -> String:
	"""
	First checks for the mime-type in the 'Content-Type' header, if that's not
	set then checks the extension of `url` and if that doesn't exist, returns
	an empty string
	"""
	var type: String = _get_content_type(response.get("headers", []))
	if type.empty():
		type = url.get_extension()
	if type.empty():
		type = FileSignature.file_type(response.get("body", PoolByteArray()))
	return type


func _get_content_type(header: Array) -> String:
	var type: String = ""
	for item in header:
		if item.begins_with("Content-Type:"):
			# Get the file type from the MIME-type
			type = item.trim_prefix("Content-Type:")
			type = type.split(";", true, 1)[0]
			if len(type.split("/", true, 1)) > 1:
				type = type.split("/", true, 1)[1]
			else:
				type = ""
			type = type.strip_edges()
			break
	return type


func _create_image_from_data(file_type: String, image_data: PoolByteArray
		) -> Image:
	var image = Image.new()
	if file_type in ["bmp", "dib"]:
		image.load_bmp_from_buffer(image_data)
	elif file_type in ["jpg", "jpeg", "jpe", "jif", "jfif", "jfi"]:
		image.load_jpg_from_buffer(image_data)
	elif file_type in ["png"]:
		image.load_png_from_buffer(image_data)
	elif file_type in ["tga", "targa", "x-tga", "x-targa", "icb", "vda", "vst"]:
		image.load_tga_from_buffer(image_data)
	elif file_type in ["webp"]:
		image.load_webp_from_buffer(image_data)
	return image


func _claim_request_node() -> int:
	"""
	Waits for a HTTPRequest node to become available, then marks it as in use.
	When finished using it should call `_release_request_node()` to mark it as
	free
	
	Returns:
		The index in `_request_nodes_pool` of the claimed request node
	"""
	yield(get_tree(), "idle_frame")
	var idx: int = -1
	for i in range(len(_request_nodes_claimed)):
		if _request_nodes_claimed[i] == false:
			_request_nodes_claimed[i] = true
			idx = i
			break
	while idx < 0:
		yield(self, "request_node_released")
		for i in range(len(_request_nodes_claimed)):
			if _request_nodes_claimed[i] == false:
				_request_nodes_claimed[i] = true
				idx = i
				break
	return idx


func _release_request_node(idx: int) -> void:
	"""
	Marks the HTTPRequest node as free to use
	
	Args:
		idx: index of the HTTPRequest node in `_request_nodes_pool`
	"""
	_request_nodes_claimed[idx] = false
	emit_signal("request_node_released")
